# etf_momentum_app.py
import streamlit as st
import akshare as ak
import pandas as pd
import numpy as np
import plotly.graph_objects as go
from plotly.subplots import make_subplots
from scipy import stats
import random
import time
import requests
from io import BytesIO
import plotly.express as px
from datetime import datetime, timezone, timedelta
from stock import stock_hist_em
from decimal import Decimal

# 初始化ETF数据库（A股+港股）
ETF_DATABASE = {
    "黄金ETF": "518880",
    "港股创新药ETF": "513120",
    "香港证券ETF": "513090",
    "中概互联ETF": "513050",
    "芯片ETF": "512760",
    "煤炭ETF": "515220",
    "沪深300ETF": "510300",
    "中证500ETF": "510500",
    "科创50ETF": "588000",
    "创业ETF": "159915",
    "军工龙头ETF": "512710",
    "机器人ETF": "562500",
    "云计算ETF": "516510",
    "电池ETF": "159755",
    "金融科技ETF": "159851",
    "纳指ETF": "159941",
    "30国债ETF": "511130",
    "证券ETF":"512880",
    "人工智能":"159819",
    "电池ETF":"159755",
    "创新药":"159992",
    "5G通信ETF":"515050",
    "消费ETF": "159928",
    "稀土ETF":"516150"
}

PE_DATABASE = {
    "510300": "000300",  # 沪深300ETF -> 沪深300指数
    "510500": "000905",  # 中证500ETF -> 中证500指数
    "588000": "000688",  # 科创50ETF -> 科创50指数
    "159915": "000958",  # 创业ETF -> 创业板指数
    "513050":"H30533"
}

PE_RANGE={
    "000300":{"high":17,"low":9,"max":600000},
    "000958":{"high":35,"low":16,"max":100000},
    "000905":{"high":33,"low":20,"max":100000},
    "000688":{"high":85,"low":40,"max":100000},
    "H30533":{"high":55,"low":23,"max":100000}
}

# 使用缓存装饰器，设置TTL为1800秒（30分钟）
@st.cache_data(ttl=1800)
def get_cached_data_with_datetime():
    # 创建UTC+8时区对象
    beijing_tz = timezone(timedelta(hours=8))
    # 获取当前时间并设置时区
    current_time = datetime.now(beijing_tz)
    return current_time

@st.cache_data(ttl=1800)
def fetch_market_data():
    try:
        # 使用AKShare获取A股市场现货数据
        stock_zh_a_spot_df = ak.stock_zh_a_spot_em()
        return stock_zh_a_spot_df
    except Exception as e:
        st.error(f"获取数据失败: {e}")
        return None

@st.cache_data(ttl=1800)
def calculate_market_overview(df):
    total_stocks = len(df)
    up_stocks = len(df[df['涨跌幅'] > 0])
    down_stocks = len(df[df['涨跌幅'] < 0])
    flat_stocks = total_stocks - up_stocks - down_stocks

    overview = {
        '总成交额(亿)': round(df['成交额'].sum() / 100000000, 2),
        '上涨家数': up_stocks,
        '下跌家数': down_stocks,
        '平盘家数': flat_stocks,
        '涨跌比': round(up_stocks / (down_stocks + 1e-5), 2),
        '平均涨跌幅': round(df['涨跌幅'].mean(), 2)
    }
    return overview

# 使用 AkShare 获取行业资金流数据
@st.cache_data(ttl=1800)
def load_count():
    try:
        return stock_hist_em.get_count()
    except Exception as e:
        st.error(f"获取数据失败: {e}")
        return {}

# 使用 AkShare 获取行业资金流数据
@st.cache_data(ttl=1800)
def load_industry_data_from_akshare():
    try:
        time.sleep(random.uniform(2,4))
        df = ak.stock_fund_flow_industry(symbol="即时")
        
        # 数据清洗与转换
        df['流入资金'] = pd.to_numeric(df['流入资金'], errors='coerce')
        df['流出资金'] = pd.to_numeric(df['流出资金'], errors='coerce')
        df['净额'] = pd.to_numeric(df['净额'], errors='coerce')
        
        df['资金方向'] = df['净额'].apply(lambda x: '流入' if x >= 0 else '流出')
        
        df.rename(columns={
            '行业': '行业名称',
            '净额': '资金净流入(亿元)',
            '行业-涨跌幅': '涨跌幅(%)',
            '流入资金': '流入资金(亿元)',
            '流出资金': '流出资金(亿元)'
        }, inplace=True)
        
        return df
    except Exception as e:
        st.error(f"获取数据失败: {e}")
        return pd.DataFrame()

def get_pe_hoding(pe, high, low):
    return (high - pe) / (high - low) * 100

@st.cache_data(ttl=1800) 
def get_pe_ratios(codes):
    results = []    
    for code in codes:
        try:
            time.sleep(random.uniform(1,2))
            url = f"https://oss-ch.csindex.com.cn/static/html/csindex/public/uploads/file/autofile/indicator/{code}indicator.xls"
            response = requests.get(url)
            response.raise_for_status()
            excel_data = BytesIO(response.content)
            df = pd.read_excel(excel_data)
            
            index_name = "未知"
            pe_value = "未找到"
            date_value = ""
            
            if len(df) > 0:
                # 查找日期列
                for col in df.columns:
                    if "日期" in str(col) or "Date" in str(col):
                        date_column = col
                        date_value = df.iloc[0][date_column] if len(df) > 0 else ""
                        break
                
                # 查找名称列
                for col in df.columns:
                    if "指数中文简称" in str(col) or "Index Chinese Name" in str(col):
                        index_name = df.iloc[0][col] if len(df) > 0 else "未知"
                        break
                
                # 查找市盈率列
                if len(df) > 1:
                    pe_column = None
                    for col in df.columns:
                        if "P/E1" in str(col):
                            pe_column = col
                            break
                    if pe_column is not None:
                        pe_value = df.iloc[0][pe_column]-1
                    else:
                        for col in df.columns:
                            if "市盈率" in str(col) or "PE" in str(col).upper():
                                pe_value = df.iloc[0][col]-1
                                break
                
            results.append({"date": date_value, "code": code, "name": index_name, "pe": pe_value})
        except Exception as e:
            results.append({"date": "", "code": code, "name": "获取失败", "pe": f"错误: {str(e)}"})
    return pd.DataFrame(results)

# 动量得分计算模型
@st.cache_data(ttl=1800)
def calculate_momentum_scores(df, date, trend_window=20):
    df_sub = df.iloc[-trend_window * 2:]
    df_current=df.iloc[-1]
    if len(df_sub) < trend_window:
        return {"错误": "数据不足"}
    
    # 1. 趋势强度因子
    x = np.arange(len(df_sub))
    y = np.log(df_sub['close'])
    slope, _, r_value, _, _ = stats.linregress(x, y)
    trend_score = (slope * 20) * (r_value ** 2)
    
    # 2. 动量因子
    roc_5 = (df_sub['close'].iloc[-1] / df_sub['close'].iloc[-11] - 1)# * 100
    roc_10 = (df_sub['close'].iloc[-1] / df_sub['close'].iloc[-21] - 1) #* 100
    momentum_score = 0.6 * roc_5 + 0.4 * roc_10
    
    # 3. 量能因子
    vol_ma_short = df_sub['volume'].rolling(5).mean().iloc[-1]
    vol_ma_long = df_sub['volume'].rolling(20).mean().iloc[-1]
    volume_score = np.log(vol_ma_short / vol_ma_long) if vol_ma_long > 0 else 0
    
    total_score = 30 * max(0,trend_score) + 20 * max(0,momentum_score) + 50 * max(0,volume_score)
    if df['close'].iloc[-1] <= df['ma14'].iloc[-1] - df['atr60'].iloc[-1]:
        total_score = total_score * 0.5
    if df['close'].iloc[-1]<= df['ma60'].iloc[-1]:
        total_score = total_score * 0.5
    
    today_amount = df['volume'].iloc[-1]
    yesterday_amount = df['volume'].iloc[-2] if len(df) > 1 else 0
    avg_5day = df['volume'].tail(10).mean() if len(df) >= 10 else today_amount

    op="--"
    amount_status = "突增" if (today_amount > yesterday_amount * 1.2 and today_amount > avg_5day) else ("低量" if today_amount < avg_5day else "--")
    if (df['close'].iloc[-1] >df['ma14'].iloc[-1] - df['atr60'].iloc[-1]
        and df['close'].iloc[-2] <df['ma14'].iloc[-2] - df['atr60'].iloc[-2]
    ) :
        op="【买】"
    if (today_amount > yesterday_amount * 1.2 and today_amount > avg_5day 
        and df['close'].iloc[-1] >df['ma14'].iloc[-1] - df['atr60'].iloc[-1]
        and df['close'].iloc[-2] <df['ma14'].iloc[-2] - df['atr60'].iloc[-2]
    ) :
        op="【追买】"

    if (df['close'].iloc[-1] <df['ma14'].iloc[-1] - df['atr60'].iloc[-1] and df['close'].iloc[-2] >df['ma14'].iloc[-1] - df['atr60'].iloc[-1]):
       op="【卖】"    

    if (df['close'].iloc[-1] <df['ma60'].iloc[-1] - df['atr60'].iloc[-1] and df['close'].iloc[-2] >df['ma60'].iloc[-1] - df['atr60'].iloc[-1]):   
        op="【清】"

    onHand="--"
    #是否持有
    #1.高于MA60
    #2.高于MA14+ATR
    #3.MA60低于MA14
    if (
        (df['close'].iloc[-1] >(df['ma14'].iloc[-1] + df['atr60'].iloc[-1]))
        and (f['close'].iloc[-1] >df['ma60'].iloc[-1])
        
    ) :
        onHand="【持】"

    if (df['close'].iloc[-1] <df['ma14'].iloc[-1] - df['atr60'].iloc[-1] ):   
        onHand="【半仓】"    

    if (df['close'].iloc[-1] <df['ma60'].iloc[-1]):   
        onHand="【必需清仓】"    

    return {
        '今日价格': df['close'].iloc[-1],
        '涨幅': f"{round(df['chp'].iloc[-1], 2)}%",
        '是否持有': onHand,
        '今日操作': op,
        '成交额情况': amount_status,
        '买入价':df['ma14'].iloc[-1] + df['atr60'].iloc[-1],
        '止损价': df['ma14'].iloc[-1] - df['atr60'].iloc[-1] ,
        '平均波动率': f"{round(df['atr20Pct'].iloc[-1] * 100, 2)}%",
        '网格建议': f"{df['wgPct'].iloc[-1]}倍波动率",
        '网格大小': f"{round(df['atr20Pct'].iloc[-1] * df['wgPct'].iloc[-1] * 100, 2)}%",        
        '标签': df['tags'].iloc[-1],
        '趋势强度': max(0,round(trend_score, 2)),
        '动量得分': max(0,round(momentum_score, 2)),
        '量能指标': max(0,round(volume_score, 2)),
        '综合评分':round(total_score, 2)
    }

# 获取历史数据（AKShare接口）
@st.cache_data(ttl=1800)
def fetch_etf_data_ak(symbol, start_date):
    try:
        #time.sleep(random.uniform(8,12))
        df=stock_hist_em.get_kline_data_sina(code=symbol,scale=240,limit=730)
        #df = ak.fund_etf_hist_em(symbol=symbol, period="daily", adjust="qfq",start_date=(datetime.now() - timedelta(days=120)).strftime('%Y%m%d'))
        
        df.set_index('date', inplace=True)
        return df[df.index >= pd.to_datetime(start_date)]
    except Exception as e:
        st.error(f"数据获取失败: {str(e)}")
        return pd.DataFrame()

def calculate_technical_indicators(df):
    """计算技术指标"""
    if df.empty:
        return df
    
    # 计算真实波幅（True Range）
    df['prev_close'] = df['close'].shift(1)
    df['tr1'] = df['high'] - df['low']
    df['tr2'] = abs(df['high'] - df['prev_close'])
    df['tr3'] = abs(df['low'] - df['prev_close'])
    #df['tr'] = df[['tr1', 'tr2', 'tr3']].max(axis=1)
    #使用收盘波幅
    df['tr'] = abs(df['close'] - df['prev_close'])
    
    # 计算20日ATR
    df['atr60'] = df['tr'].rolling(window=60).mean()
    df['atr20Pct'] = df['atr60'] / df['close']
    
    # 计算移动平均线
    df['ma5'] = df['close'].rolling(window=5).mean()
    df['ma14'] = df['close'].rolling(window=14).mean()
    df['ma20'] = df['close'].rolling(window=20).mean()
    df['ma60'] = df['close'].rolling(window=60).mean()
    
    # 计算止损价
    df['cutPrice'] = df['ma14'] - df['atr60']
    
    
    # 计算标签和网格建议
    df['tags'] = np.where(df['close'] <= df['cutPrice'], '低于半仓价', '--')
    df['tags'] = np.where(df['close'] <= df['ma60'], '低于止损价', df['tags'])
    df['wgPct'] = np.where(df['close'] >= df['ma14'], 1, 
                          np.where(df['close'] <= df['ma60'], 3, 2))
    
    return df

def generate_plotly_chart(df, days=730):
    """生成带移动平均线的K线图"""
    df = df.tail(days).copy()
    if df.empty:
        return None
    
    fig = make_subplots(
        rows=2, cols=1,
        shared_xaxes=True,
        vertical_spacing=0.1,
        row_heights=[0.7, 0.3]
    )
    
    # 添加K线图
    fig.add_trace(
        go.Candlestick(
            x=df.index,
            open=df['open'],
            high=df['high'],
            low=df['low'],
            close=df['close'],
            name='K线',
            increasing_line_color='#ef5350',
            decreasing_line_color='#26a69a'
        ),
        row=1, col=1
    )
    
    # 添加移动平均线
    for ma_col, color, name in [
                                ('ma60', '#FF0000', '生命线'), 
                                #('ma14', '#ff7f0e', '14日均线'),
                                ('cutPrice', '#ffcc00', '止损线')]:
        if ma_col in df.columns:
            fig.add_trace(
                go.Scatter(
                    x=df.index,
                    y=df[ma_col],
                    name=name,
                    line=dict(color=color, width=1.5),
                    opacity=0.8
                ),
                row=1, col=1
            )
    
    # 添加成交量
    fig.add_trace(
        go.Bar(
            x=df.index,
            y=df['volume'],
            name='成交量',
            marker_color='#7f7f7f',
            opacity=0.6
        ),
        row=2, col=1
    )
    
    fig.update_layout(
        title=f'最近{days}个交易日走势',
        height=600,
        template='plotly_white',
        margin=dict(l=50, r=50, t=60, b=50)
    )
    fig.update_xaxes(rangeslider_visible=False)
    
    return fig

def initialize_session_state():
    """初始化session state"""
    if 'data_loaded' not in st.session_state:
        st.session_state.data_loaded = False
    if 'market_data' not in st.session_state:
        st.session_state.market_data = None
    if 'industry_data' not in st.session_state:
        st.session_state.industry_data = None
    if 'load_count' not in st.session_state:
        st.session_state.load_count = None
    if 'pe_data' not in st.session_state:
        st.session_state.pe_data = None
        

def load_initial_data():
    get_cached_data_with_datetime()
    """加载初始数据"""
    with st.spinner("正在加载市场数据..."):
        # 加载市场数据
        #if st.session_state.market_data is None:
        #    st.session_state.market_data = fetch_market_data()
        
        # 加载行业数据
        if st.session_state.industry_data is None:
            st.session_state.industry_data = load_industry_data_from_akshare()
        
        # 加载行业数据
        if st.session_state.load_count is None:
            st.session_state.load_count = load_count()
        
        # 加载PE数据
        if st.session_state.pe_data is None:
            pe_etfs = list(PE_DATABASE.values())
            st.session_state.pe_data = get_pe_ratios(tuple(pe_etfs))
            
            # 处理PE数据
            if not st.session_state.pe_data.empty:
                st.session_state.pe_data['holdingPct'] = np.nan
                valid_codes = st.session_state.pe_data['code'].isin(PE_RANGE.keys())
                
                st.session_state.pe_data.loc[valid_codes, 'holdingPct'] = get_pe_hoding(
                    st.session_state.pe_data.loc[valid_codes, 'pe'],
                    st.session_state.pe_data.loc[valid_codes, 'code'].map(lambda x: PE_RANGE[x]['high']),
                    st.session_state.pe_data.loc[valid_codes, 'code'].map(lambda x: PE_RANGE[x]['low'])
                )
               # 计算 holdingMoney 并转换为万元单位（保留2位小数）
                st.session_state.pe_data.loc[valid_codes, 'holdingMoney'] = (
                    st.session_state.pe_data.loc[valid_codes, 'holdingPct'] *
                    st.session_state.pe_data.loc[valid_codes, 'code'].map(lambda x: PE_RANGE[x]['max']) /
                    1000000  # 原始除以100，再除以10000转换为万元
                )

                # 可选：创建带单位的格式化列
                st.session_state.pe_data.loc[valid_codes, 'holdingMoney'] = (
                    st.session_state.pe_data.loc[valid_codes, 'holdingMoney']
                    .apply(lambda x: f"{x:.2f}万元")
                )
                
                
                st.session_state.pe_data = st.session_state.pe_data.rename(columns={
                    'date': '日期', 'code': '代码', 'name': '名称', 
                    'pe': '市盈率', 'holdingPct': '建议持仓比例%','holdingMoney': '持仓金额'
                })
        
        st.session_state.data_loaded = True

def display_market_overview():
    """显示市场概览"""
    if st.session_state.market_data is not None:
        overview = calculate_market_overview(st.session_state.market_data)
        st.subheader("📊 市场概览")        
        col1, col2, col3, col4 = st.columns(4)
        col1.metric("总成交额(亿)", overview['总成交额(亿)'])
        col2.metric("涨跌比", overview['涨跌比'])
        col3.metric("平均涨跌幅", f"{overview['平均涨跌幅']}%")
        col4.metric("上涨占比", 
                   f"{round(overview['上涨家数'] / (overview['上涨家数'] + overview['下跌家数']) * 100, 2)}%")

def display_industry_flow():
    """显示行业资金流"""
    if not st.session_state.industry_data.empty:
        st.subheader("📊 资金流入流出")
        col1, col2, col3, col4 = st.columns(4)
        
        total_inflow = st.session_state.industry_data['流入资金(亿元)'].sum()
        total_outflow = st.session_state.industry_data['流出资金(亿元)'].sum()
        net_inflow = st.session_state.industry_data['资金净流入(亿元)'].sum()
        total_volume = total_inflow + total_outflow
        
        with col1:
            st.metric("净资金流入", f"{net_inflow:+.2f}亿元")
        with col2:
            st.metric("总成交量", f"{total_volume:.2f}亿元") 
        with col3:
            st.metric("总流入金额", f"{total_inflow:.2f}亿元")
        with col4:
            st.metric("总流出金额", f"{total_outflow:.2f}亿元")
        
        # 行业资金流向图表
        col_left, col_middle, col_right = st.columns([2, 2, 2])
        
        with col_left:
            st.subheader("💰 行业资金流向分布")
            chart_df = st.session_state.industry_data.sort_values('资金净流入(亿元)', ascending=True).copy()
            chart_df_filtered = chart_df[
                 (chart_df['资金净流入(亿元)'] >= 18) |
                (chart_df['资金净流入(亿元)'] <= -18)
            ]
            
            fig = px.bar(
                chart_df_filtered,
                x='资金净流入(亿元)',
                y='行业名称',
                color='资金方向',
                color_discrete_map={'流入': '#FF0000', '流出': '#00B050'},
                orientation='h',
                height=400,
                title="各行业资金净流入情况"
            )
            st.plotly_chart(fig, use_container_width=True)
        
        with col_middle:
            st.subheader("📋 资金流入行业")
            filtered_df_in = st.session_state.industry_data[
                (st.session_state.industry_data['资金净流入(亿元)'] >= 18)
            ].sort_values('资金净流入(亿元)', ascending=False)
            
            st.dataframe(
                filtered_df_in[['行业名称', '资金净流入(亿元)', '涨跌幅(%)']],
                use_container_width=True,
                height=400
            )
        
        with col_right:
            st.subheader("📋 资金流出行业")
            filtered_df_out = st.session_state.industry_data[
                (st.session_state.industry_data['资金净流入(亿元)'] <= -18)
            ].sort_values('资金净流入(亿元)', ascending=True)
            
            st.dataframe(
                filtered_df_out[['行业名称', '资金净流入(亿元)', '涨跌幅(%)']],
                use_container_width=True,
                height=400
            )

def display_volume():
    sh=stock_hist_em.get_now(codes='s_sh000001')
    sz=stock_hist_em.get_now(codes='s_sz399106')
    total_sum = np.sum(sh['volume'].values + sz['volume'].values)

    st.subheader("📝 今日成交量")
    col1,col2 = st.columns(2)
    with col1:
        st.metric("当前成交量", f"{(total_sum/100000000):.1f}亿")
    with col2:
        st.metric("1.5倍成交量", f"{(total_sum*Decimal(1.5)/100000000):.1f}亿")

def display_load_count():
    """显示涨跌比"""
    if st.session_state.load_count is not None :
        st.subheader("📝 今日涨跌比")
        # 将字典转换为DataFrame以便更好显示
        count_data = st.session_state.load_count
        total = count_data['up'] + count_data['eq'] + count_data['down']
        
        # 创建更友好的显示数据
        display_data = {
            '统计类型': ['上涨家数', '平盘家数', '下跌家数', '总计'],
            '数量': [
                count_data['up'], 
                count_data['eq'], 
                count_data['down'],
                total
            ],
            '占比': [
                f"{(count_data['up']/total*100):.1f}%" if total > 0 else "0%",
                f"{(count_data['eq']/total*100):.1f}%" if total > 0 else "0%",
                f"{(count_data['down']/total*100):.1f}%" if total > 0 else "0%",
                "100%"
            ]
        }
        col1, col2, col3, col4 = st.columns(4)
        with col1:
            st.metric("上涨比例", f"{(count_data['up']/total*100):.1f}%" if total > 0 else "0%")
    
        with col2:
            st.metric("上涨家数", f"{count_data['up']}家" if total > 0 else "0%")

        with col3:
            st.metric("下跌比例", f"{(count_data['down']/total*100):.1f}%" if total > 0 else "0%")
    
        with col4:
            st.metric("下跌家数", f"{count_data['down']}家" if total > 0 else "0%")
             

def display_pe_recommendations():
    """显示PE持仓建议"""
    if st.session_state.pe_data is not None and not st.session_state.pe_data.empty:
        st.subheader("📝 宽基持仓建议")
        st.dataframe(st.session_state.pe_data, use_container_width=True)

def analyze_etf_momentum(selected_etfs, selected_date, start_date, trend_window, 
                        trend_weight, momentum_weight, volume_weight):
    """分析ETF动量"""
    results = []
    charts = []
    
    progress_bar = st.progress(0)
    
    for i, etf_name in enumerate(selected_etfs):
        progress = (i + 1) / len(selected_etfs)
        progress_bar.progress(progress, text=f"处理 {etf_name}...")
        
        symbol = ETF_DATABASE[etf_name]
        df = fetch_etf_data_ak(symbol, start_date.strftime("%Y-%m-%d"))
        
        if df.empty:
            st.warning(f"{etf_name}({symbol}) 数据获取失败，跳过")
            continue
            
        df.sort_index(inplace=True)
        df = calculate_technical_indicators(df)
        
        if df.empty:
            continue
            
        scores = calculate_momentum_scores(df, selected_date.strftime("%Y-%m-%d"), trend_window)
        
        # 动态调整权重
        total_score = (
            max(0,trend_weight * scores["趋势强度"])+
            max(0,momentum_weight * scores["动量得分"]) +
            max(0,volume_weight * scores["量能指标"])
        )
        if df['close'].iloc[-1] <= df['ma14'].iloc[-1] - df['atr60'].iloc[-1]:
            total_score = total_score * 0.5
        if df['close'].iloc[-1] <= df['ma60'].iloc[-1]:
            total_score = total_score * 0.5
        scores["综合评分"] = round(total_score, 2)
        
        # 生成图表
        fig = generate_plotly_chart(df)
        if fig:
            charts.append(fig)
        
        results.append({
            "ETF": etf_name,
            "代码": symbol,
            **scores
        })
    
    progress_bar.empty()
    return results, charts

def display_etf_results(results, charts, selected_etfs):
    """显示ETF分析结果"""
    if not results:
        st.error("所有ETF数据获取失败，请检查网络连接或代码配置")
        return
        
    df_results = pd.DataFrame(results)
    df_results = df_results.sort_values(by='综合评分', ascending=False)
    df_results["推荐权重"] = df_results["综合评分"] / df_results["综合评分"].sum()
    
    # 高亮显示最佳ETF
    def highlight_max(s):
        is_max = s == s.max()
        return ['background-color: #a1d99b' if v else '' for v in is_max]

    def highlight_top3(s):
        # 获取前三大的值（包括并列情况）
        top3_values = s.nlargest(3).values
        # 检查每个值是否在前三大的值中
        return ['background-color: #a1d99b' if v in top3_values else '' for v in s]
    
    st.subheader("📝 ETF动量评分结果")
    
    # 创建可点击的链接列
    df_display = df_results.copy()
    df_display["链接"] = df_results["代码"].apply(lambda x: f'http://8.138.24.62:8080/stock_minute.html?code={x}')

    st.data_editor(
        df_display.style.apply(highlight_top3, subset=["综合评分"]).format({"推荐权重": "{:.2%}"}),
        height=min(600, 45 * len(df_results)),
        width='stretch',
        hide_index=True,
        disabled=True,  # 设置为只读
        column_config={
            "链接": st.column_config.LinkColumn("链接", display_text="点击查看")
        }
    )
    
    # 显示最佳ETF建议
    if not df_results.empty:
        best_etf = df_results.loc[df_results["综合评分"].idxmax(), "ETF"]
        best_weight = df_results.loc[df_results["综合评分"].idxmax(), "推荐权重"]
        st.success(f"**策略建议**：优先配置 **{best_etf}**，建议仓位 **{best_weight:.1%}**")
    
    # 显示图表
    if charts:
        st.subheader("📈 K线趋势分析")
        for idx, fig in enumerate(charts):
            st.plotly_chart(fig, use_container_width=True)
            st.caption(f"{selected_etfs[idx]} 技术图表")

# 主应用界面
def today_etf(selected_etfs, selected_date, start_date, trend_window, trend_weight, momentum_weight, volume_weight):
    results, charts = analyze_etf_momentum(
        selected_etfs, selected_date, start_date, trend_window,
        trend_weight, momentum_weight, volume_weight
    )
    display_etf_results(results, charts, selected_etfs)
    return charts


def app():
    st.title("📊 今日概况")
    
    # 初始化session state
    initialize_session_state()
    
    # 自动加载数据
    if not st.session_state.data_loaded:
        load_initial_data()
        
    
    
        
    # 显示已加载的数据
    if st.session_state.data_loaded:
        # 获取缓存的时间
        cached_time = get_cached_data_with_datetime()
        st.caption(f"数据最后更新: {cached_time.strftime('%Y-%m-%d %H:%M:%S')}")
        # 缓存控制
        if st.button("🔄 刷新"):
            st.cache_data.clear()
            st.session_state.clear()
            st.rerun()
        display_volume()
        display_market_overview()
        display_load_count()
        display_industry_flow()
        display_pe_recommendations()
        
        # 日期选择器
        max_date = datetime.now() 
        selected_date = st.date_input(
            "选择评估日期",
            value=max_date,
            max_value=max_date
        )
         # ETF多选
        selected_etfs = st.multiselect(
            "选择ETF",
            options=list(ETF_DATABASE.keys()),
            default=list(ETF_DATABASE.keys())
        )
        
        
        
        # 高级参数
        with st.expander("高级设置"):
            trend_window = st.slider("趋势计算窗口(日)", 20, 60, 20)
            start_date = st.date_input("数据开始日期", value=selected_date - timedelta(days=365))
            
            st.markdown("**因子权重调整**")
            trend_weight = st.slider("趋势强度权重", 0, 100, 30)
            momentum_weight = st.slider("动量得分权重", 0, 100, 20)
            volume_weight = st.slider("量能指标权重", 0, 100, 50)
            
            # 缓存控制
            if st.button("🔄 清除缓存"):
                st.cache_data.clear()
                st.session_state.clear()
                st.rerun()
        charts = today_etf(selected_etfs, selected_date, start_date, trend_window, trend_weight, momentum_weight, volume_weight)

        # ETF动量分析
        if st.button("🚀 生成ETF动量分析", type="primary", use_container_width=True):
            if not selected_etfs:
                st.warning("请至少选择一个ETF进行分析")
                return
                
            charts = today_etf(selected_etfs, selected_date, start_date, trend_window, trend_weight, momentum_weight, volume_weight)
    else:
        st.info("正在加载数据，请稍候...")
        

# 应用入口
if __name__ == "__main__":
    st.set_page_config(
        page_title="ETF轮动分析",
        layout="wide",
        page_icon="📈",
        initial_sidebar_state="expanded"
    )
    app()